노션,피그마,드리블,깃허브,비핸스,링크드인 연동#2 | 매거진에 참여하세요

인사이트/로그개발 관련
작성일 : 23.12.13

노션,피그마,드리블,깃허브,비핸스,링크드인 연동#2

#드리블 #피그마 #피그마dev #깃허브 #연동 #api #사이트연동 #oauth #dribbble #연동데이터

👉 본문을 50%이상을 읽으면 '여기까지다' 퀘스트가 완료됩니다(로그인 필수)

지난번의 이야기

지난번에 말씀드린대로, 렛플에서는 외부 사이트의 API를 활용해서 저희가 얻지 못하는 데이터를 렛플에 접목하기로 하였습니다.

노션, 피그마, 깃허브, 노트폴리오, 드리블, 비핸스, 링크드인 등 총 일곱개의 서비스를 검토했습니다.

이중, 노션, 피그마, 깃허브, 드리블 등은 문서 구조가 잘 되어있어서 활용하기 좋은것으로 최종 판단했습니다.

상세 내용은 아래 참고하세요

https://letspl.me/quest/653

이번 이야기

최종적으로 세가지 서비스와의 연동을 결정하게 되었습니다. - 피그마, 깃허브, 드리블

노션을 제외한 이유의 경우,다음과 같습니다.

  • - 저희가 블로그가 메인 서비스는 아니다보니, 노션 페이지를 연동했을때 얻을 수 있는 이점이 적다.

  • - 만약 웹훅으로 변경/댓글이벤트만 받는다고 하더라도 연동 방식이 너무 복잡하여 사용자의 노력이 많이 들기 때문입니다.

( 사용자에게 불편한 UX를 경험시키면서 연동시키는것은 큰 의미가 없습니다.)

해당 서비스를 어떻게 연동할 수 있는지 하나씩 풀어서 적어보도록 하겠습니다.


1. 드리블

포트폴리오 연동

드리블과 연동을 하기 위해서는, 드리블에 사이트 정보등을 기입해야 합니다.

https://developer.dribbble.com/

기본적인 정보와 , callbackURL을 등록하면 Client ID과 Client Secret이 발생되고 이를 이용해서 통신을 하는 구조입니다.

스크래치부터 만들어서 연동하기 싫기 때문에, 패스포트를 검색해봅니다.

렛플의 회원가입은 모두 패스포트를 활용해서 개발이 되어있습니다.(네이버/구글/카카오/페이스북)

그냥 웹통신으로 해도되는데, 패스포트로 하니 일원화 관리가 쉬운것 같습니다.

https://www.passportjs.org/packages/passport-dribbble/

드리블의 경우, 권한이 세분화되어있지 않아서 그냥 public 권한으로 신청합니다.

기존 패키지를 v2를 바라보도록 수정합니다.
node_modules/passport-dribbble/lib/passport-dribbble/strategy.js

-  this._oauth2.get('https://api.dribbble.com/v1/user', accessToken, function (err, body, res) {
+  this._oauth2.get('https://api.dribbble.com/v2/user', accessToken, function (err, body, res) {


node.js

router.get(
  "주소가 들어갑니다",
  passport.authenticate("dribbble", {
    scope: "public",
    failureRedirect: "/",
  }),
  function (req, res) {
    console.log(req)
  }
);

위의 req의 회신으로 access_token 및 refresh_token 등 사용자와 관련한 정보를 받습닏.

그리고 위의 호출에서 받은 access_token으로 등록된 shots에 대한 정보를 호출합니다.

request.get(
  {
    url: `https://api.dribbble.com/v2/user/shots?access_token=${token}`,
    headers: {
      Accept: "application/json",
    },
  },
  function (error, response, body) {
    if (!error && response.statusCode == 200) {
      console.log(body)
    }
  }
);

이렇게 호출하게 되면, 유저가 공개프로필로 설정한 shots 정보를 JsonArray 방식으로 제공해줍니다.

썸네일로 쓸 수 있는 이미지와 함께, 이미지의 태그, 그리고 상세한 attachments나 관련한 project 리스트를 가지고 올 수 있습니다.

또한 바로가기를 지원할 수 있는 html_url 등도 같이 보내줍니다.

이러면 좀 더 풍부한 포트폴리오를 보여줄 수 있겠죠

[
  {
    animated: false,
    description: null,
    height: 797,
    html_url: 'https://dribbble.com/shots/23178264-content-editor-within-LETSPL',
    id: 23178264,
    images: {
      hidpi: 'https://cdn.dribbble.com/userupload/11742959/file/original-60efb910006a8625682f554d21cd1fe3.jpg?resize=731x797',
      normal: 'https://cdn.dribbble.com/userupload/11742959/file/original-60efb910006a8625682f554d21cd1fe3.jpg?resize=400x300',
      one_x: 'https://cdn.dribbble.com/userupload/11742959/file/original-60efb910006a8625682f554d21cd1fe3.jpg?resize=400x300',
      teaser: 'https://cdn.dribbble.com/userupload/11742959/file/original-60efb910006a8625682f554d21cd1fe3.jpg?resize=200x150'
    },
    low_profile: false,
    tags: [ 'button', 'editor', 'html editor', 'mark', 'ui' ],
    title: 'content editor within LETSPL',
    width: 731,
    published_at: '2023-12-04T08:11:56Z',
    updated_at: '2023-12-13T00:55:16Z',
    attachments: [],
    projects: [],
    video: null
  }
]

이를 잘 정리하면 다음과 같이 포트폴리오 정보를 프로필에서 직접 보여줄 수 있습니다.

https://letspl.me/people/%EB%A0%9B%ED%94%8C%EC%9A%B4%EC%98%81%EC%9E%90


2. 깃허브

  1. 1) 저장소 연동

  2. 연동을 하기위해서는 , 깃허브쪽에 연동관련한 계정설정을 해줘야 합니다.

  3. https://github.com/settings/developers

깃허브에 보면 “GITHUB APP”과 “OAuth APP" 두개가 있는데요.

설명을 몇번 읽어봐도 제대로 잘 이해되진 않았는데, 조금 더 깃헙을 제대로 연동하려면 “GITHUB APP”을 써야하는것으로 이해했습니다.

인증하는 과정등도 조금 더 까다로운 느낌이었어서,

단순히 웹훅을 설정하거나, 기본적인 정보를 불러들이는것은 “OAuth App”으로 충분할것 같아, 저희는 “OAuth App”으로 진행하였습니다.

설정부분은 기본적인 SNS 연동과 동일합니다.

기본적인 정보와 , callbackURL을 등록하면 Client ID과 Client Secret이 발생되고 이를 이용해서 통신을 하는 구조입니다.

한땀 한땀 연동할 수 있지만, 저희 모든 SNS 연동이 Passport를 이용하기 때문에, 잘 만들어진 Passport를 찾아봅니다.

역시 깃허브는 잘되어있네요.

https://www.passportjs.org/packages/passport-github2/

저희는 권한을 아래와 같이 , 레포지토리, 프로젝트, 훅을 읽어오는 것으로만 정의했습니다.

레포지토리에 관련한 정보가 필요하고, 어떤 이벤트가 최신에 있었는지 확인이 필요하기 때문에 , 이렇게 세개를 요청하면 될것 같다는 생각입니다.

정보가 한번 호출로, 세부적인 정보를 받기는 어렵고, 각각 필요할때마다 호출을 해줘야 하는 구조입니다.

회신 받은 _json에 담긴 유저 아이디를 활용해서 호출하면 됩니다.

router.get(
  "*************",
  passport.authenticate("github", {
    scope: ["public_repo", "read:project", "read:repo_hook"],
    failureRedirect: "/",
  }),
  function (req, res) {
    console.log(req)
  }
);

이벤트 : https://api.github.com/users/[유저아이디]/events

: 최근 푸시나 풀 등 깃허브에 등록된 이벤트 등이 등록되어있습니다.

JSONArray 방식으로 내용을 확인할 수 있습니다.

만약에 푸시일 경우 아래와 같이 커밋 정보가 있게 되고, 커밋 정보안의 message를 긁어오게 되면 어떤 내용으로 푸시를 했는지 알 수 있습니다.

payload.commits[0].message

다만 항상 commits의 정보가 있는것은 아니니, 상태 값 확인하셔서 이용하면 되겠습니다.

[
  {
    id: '33967838516',
    type: 'CreateEvent',
    actor: {
      id: 17338824,
      login: 'gibuhapi2',
      display_login: 'gibuhapi2',
      gravatar_id: '',
      url: 'https://api.github.com/users/gibuhapi2',
      avatar_url: 'https://avatars.githubusercontent.com/u/17338824?'
    },
    repo: {
      id: 728558542,
      name: 'gibuhapi2/LETSPL_PUBLIC_REPO3',
      url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO3'
    },
    payload: {
      ref: null,
      ref_type: 'repository',
      master_branch: 'main',
      description: '렛플에서 깃헙 연동을 위하여 테스트로 만든 저장소입니다. 해당 저장소는 공개일때만 불러올 수 있습니다.',
      pusher_type: 'user'
    },
    public: true,
    created_at: '2023-12-07T07:44:44Z'
  },
  {
    id: '33855667297',
    type: 'PushEvent',
    actor: {
      id: 17338824,
      login: 'gibuhapi2',
      display_login: 'gibuhapi2',
      gravatar_id: '',
      url: 'https://api.github.com/users/gibuhapi2',
      avatar_url: 'https://avatars.githubusercontent.com/u/17338824?'
    },
    repo: {
      id: 706082902,
      name: 'gibuhapi2/LETSPL_PUBLIC_REPO2',
      url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO2'
    },
    payload: {
      repository_id: 706082902,
      push_id: 16107065406,
      size: 1,
      distinct_size: 1,
      ref: 'refs/heads/main',
      head: '
f795a848b59cb18256157de5632e5be1fc448f3d',
      before: '61e06ef6260d8a8c2db65d7d19b7a0e42d76568f',
      commits: [Array]
    },
    public: true,
    created_at: '2023-12-04T07:50:23Z'
  },

저장소 : https://api.github.com/users/[유저아이디]/repos

: 유저가 공개한 저장소에 대한 각종 정보를 확인할 수 있는 정보와 URL을 회신합니다.

JSONArray 방식으로 내용을 확인할 수 있습니다.

아래에 보면 url이 직접 명시되어있고, 또한 해당 저장소의 이벤트나, 설명, 언어 정보등도 기재되어있습니다.

내용이 하두 많기 때문에 필요한 정보로 각각 url을 호출하시면 됩니다.

[
  {
    id: 706081694,
    node_id: 'R_kgDOKhXzng',
    name: 'LETSPL_PUBLIC_REPO1',
    full_name: 'gibuhapi2/LETSPL_PUBLIC_REPO1',
    private: false,
    owner: {
      login: 'gibuhapi2',
      id: 17338824,
      node_id: 'MDQ6VXNlcjE3MzM4ODI0',
      avatar_url: 'https://avatars.githubusercontent.com/u/17338824?v=4',
      gravatar_id: '',
      url: 'https://api.github.com/users/gibuhapi2',
      html_url: 'https://github.com/gibuhapi2',
      followers_url: 'https://api.github.com/users/gibuhapi2/followers',
      following_url: 'https://api.github.com/users/gibuhapi2/following{/other_user}',
      gists_url: 'https://api.github.com/users/gibuhapi2/gists{/gist_id}',
      starred_url: 'https://api.github.com/users/gibuhapi2/starred{/owner}{/repo}',
      subscriptions_url: 'https://api.github.com/users/gibuhapi2/subscriptions',
      organizations_url: 'https://api.github.com/users/gibuhapi2/orgs',
      repos_url: 'https://api.github.com/users/gibuhapi2/repos',
      events_url: 'https://api.github.com/users/gibuhapi2/events{/privacy}',
      received_events_url: 'https://api.github.com/users/gibuhapi2/received_events',
      type: 'User',
      site_admin: false
    },
    html_url: 'https://github.com/gibuhapi2/LETSPL_PUBLIC_REPO1',
    description: '렛플에서 깃헙 연동을 위하여 테스트로 만든 저장소입니다. 해당 저장소는 공개일때만 불러올 수 있습니다.',
    fork: false,
    url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1',
    forks_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/forks',
    keys_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/keys{/key_id}',
    collaborators_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/collaborators{/collaborator}',
    teams_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/teams',
    hooks_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/hooks',
    issue_events_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/issues/events{/number}',
    events_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/events',
    assignees_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/assignees{/user}',
    branches_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/branches{/branch}',
    tags_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/tags',
    blobs_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/git/blobs{/sha}',
    git_tags_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/git/tags{/sha}',
    git_refs_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/git/refs{/sha}',
    trees_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/git/trees{/sha}',
    statuses_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/statuses/{sha}',
    languages_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/languages',
    stargazers_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/stargazers',
    contributors_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/contributors',
    subscribers_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/subscribers',
    subscription_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/subscription',
    commits_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/commits{/sha}',
    git_commits_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/git/commits{/sha}',
    comments_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/comments{/number}',
    issue_comment_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/issues/comments{/number}',
    contents_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/contents/{+path}',
    compare_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/compare/{base}...{head}',
    merges_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/merges',
    archive_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/{archive_format}{/ref}',
    downloads_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/downloads',
    issues_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/issues{/number}',
    pulls_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/pulls{/number}',
    milestones_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/milestones{/number}',
    notifications_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/notifications{?since,all,participating}',
    labels_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/labels{/name}',
    releases_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/releases{/id}',
    deployments_url: 'https://api.github.com/repos/gibuhapi2/LETSPL_PUBLIC_REPO1/deployments',
    created_at: '2023-10-17T09:14:14Z',
    updated_at: '2023-12-07T07:45:38Z',
    pushed_at: '2023-12-04T07:49:45Z',
    git_url: 'git://github.com/gibuhapi2/LETSPL_PUBLIC_REPO1.git',
    ssh_url: 'git@github.com:gibuhapi2/LETSPL_PUBLIC_REPO1.git',
    clone_url: 'https://github.com/gibuhapi2/LETSPL_PUBLIC_REPO1.git',
    svn_url: 'https://github.com/gibuhapi2/LETSPL_PUBLIC_REPO1',
    homepage: 'https://letspl.me/people/렛플운영자',
    size: 15,
    stargazers_count: 0,
    watchers_count: 0,
    language: 'HTML',
    has_issues: true,
    has_projects: true,
    has_downloads: true,
    has_wiki: true,
    has_pages: false,
    has_discussions: false,
    forks_count: 0,
    mirror_url: null,
    archived: false,
    disabled: false,
    open_issues_count: 0,
    license: null,
    allow_forking: true,
    is_template: false,
    web_commit_signoff_required: false,
    topics: [ 'growth', 'learning', 'letspl', 'project' ],
    visibility: 'public',
    forks: 0,
    open_issues: 0,
    watchers: 0,
    default_branch: 'main'
  },

저장소 언어 : https://api.github.com/users/[유저아이디]/[저장소명]/languages

: 저장소별로 어떠한 언어를 많이 사용하는지 불러 올 수 있습니다. 저장소명이 필수이기 때문에,

저희는 저장소를 호출한다음, 저장소 위치별로 언어 주소를 반복하여 호출하였습니다.

이경우, 아래와 같이 저장소에 대한 코드 라인수라고 생각이 되는, 정량화된 수치를 던져줍니다.

그러면 이것을 백분율로 구해서 , 해당 저장소에 어떠한 언어를 가장 많이 사용했는지 사용할 수 있습니다.

{ HTML: 19126, JavaScript: 91 }

이것을 이용하면 가장 최신에 한 푸시등은 무엇이고, 어떤 저장소을 운영하고 있는지 실제 개발언어는 무엇인지를 한눈에 보여줄 수 있습니다.

  1. 2) 웹훅 연동

깃허브의 연동은 , 사용자가 직접 특정 URL을 등록하면 되면 구조로 되어있습니다.

그렇기 때문에 사실 OAuth와 같은 권한을 받는 작업이 필요하지 않습니다. 유저분들께 제대로 된 안내를 해드리면 되니까요.

다만 여기서 고민을 해봐야하는 부분이 생기게 됩니다.

URL이라면 쉽게 해킹이 될수도 있기 때문에 , 어떻게 하면 해킹이 되지 않는 유일무이한 주소를 만들어낼 것인가

역시 암호화죠. 렛플내에 있는 정보를 활용하여, 복호화가 불가능한 일방향 SHA 암호화를 해서 프로젝트 해시값을 만들기로 했습니다.

https://letspl.me/api/github/3480fc0****************************

이렇게 모임마다 특정 해시값을 넣어서 구분할 수 있도록 , 독립값을 모임 생성시 부여합니다.

그리고 이것을 통신시에 unique 값으로 제공해주면 되겠습니다.

웹훅을 등록하게 되면 아래와 같이 메시지가 오게됩니다.

크게는 아래와 같이 정리할 수 있는데, 어떤건 오고 안올때가 있습니다.

const {
      action,
      ref,
      before,
      after,
      repository,
      pusher,
      sender,
      compare,
      commits,
      head_commit,
    } = req.body;

repository : 어떤 저장소에 변화가 있는지

pusher: 누가 푸시한건지

sender: 이벤트를 보낸사람은 누군지

compare: 푸시를 했다면, 변경사항을 비교해 볼 수 있는 URL

head_commit : 어떤 변동사항이 있는지(커밋 메시지)

등 너무 많아서 뭐 일일이 말도 못할 정도입니다.

위의 내용들을 잘 조합해서, 웹훅 이벤트를 , 자신의 사이트와 연동을 하시면 됩니다.

다만 pusher나 head_commit이 없는 상태로 오기도 합니다. compare도 없기도 합니다. 이벤트마다 분기를 쳐줘야 하는데요

이벤트에 대한 상세 가이드가 없습니다. ㅎㅎㅎㅎ

들어오는거 보면서 해야될것 같네요

{
   "ref": "refs/heads/master",
   "before": "2f2c8ca737757c8af77102e6ee01f9c2ed31f28a",
   "after": "613a3fb5736e25e3378b33d6df6398c9ec9f064",
   "repository": {
     "id": 247432377,
     "node_id": "MDEwOlJlcG9zaXRvcnkyNDc0MzIzNzc=",
     "name": "LETSPL_PUBLIC_REPO3",
     "full_name": "letspl/LETSPL_PUBLIC_REPO3",
     "private": true,
     "owner": {
       "name": "letspl",
       "email": "help@letspl.me",
       "login": "letspl",
       "id": 17338824,
       "node_id": "MDQ6VXNlcjE3MzM4ODI0",
       "avatar_url": "https://avatars.githubusercontent.com/u/17338824?v=4",
       "gravatar_id": "",
       "url": "https://api.github.com/users/letspl",
       "html_url": "https://github.com/letspl",
       "followers_url": "https://api.github.com/users/letspl/followers",
       "following_url": "https://api.github.com/users/letspl/following{/other_user}",
       "gists_url": "https://api.github.com/users/letspl/gists{/gist_id}",
       "starred_url": "https://api.github.com/users/letspl/starred{/owner}{/repo}",
       "subscriptions_url": "https://api.github.com/users/letspl/subscriptions",
       "organizations_url": "https://api.github.com/users/letspl/orgs",
       "repos_url": "https://api.github.com/users/letspl/repos",
       "events_url": "https://api.github.com/users/letspl/events{/privacy}",
       "received_events_url": "https://api.github.com/users/letspl/received_events",
       "type": "User",
       "site_admin": false
     },
     "html_url": "https://github.com/letspl/LETSPL_PUBLIC_REPO3",
     "description": null,
     "fork": false,
     "url": "https://github.com/letspl/LETSPL_PUBLIC_REPO3",
     "forks_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/forks",
     "keys_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/keys{/key_id}",
     "collaborators_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/collaborators{/collaborator}",
     "teams_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/teams",
     "hooks_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/hooks",
     "issue_events_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/issues/events{/number}",
     "events_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/events",
     "assignees_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/assignees{/user}",
     "branches_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/branches{/branch}",
     "tags_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/tags",
     "blobs_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/git/blobs{/sha}",
     "git_tags_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/git/tags{/sha}",
     "git_refs_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/git/refs{/sha}",
     "trees_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/git/trees{/sha}",
     "statuses_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/statuses/{sha}",
     "languages_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/languages",
     "stargazers_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/stargazers",
     "contributors_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/contributors",
     "subscribers_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/subscribers",
     "subscription_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/subscription",
     "commits_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/commits{/sha}",
     "git_commits_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/git/commits{/sha}",
     "comments_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/comments{/number}",
     "issue_comment_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/issues/comments{/number}",
     "contents_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/contents/{+path}",
     "compare_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/compare/{base}...{head}",
     "merges_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/merges",
     "archive_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/{archive_format}{/ref}",
     "downloads_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/downloads",
     "issues_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/issues{/number}",
     "pulls_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/pulls{/number}",
     "milestones_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/milestones{/number}",
     "notifications_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/notifications{?since,all,participating}",
     "labels_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/labels{/name}",
     "releases_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/releases{/id}",
     "deployments_url": "https://api.github.com/repos/letspl/LETSPL_PUBLIC_REPO3/deployments",
     "created_at": 1584262764,
     "updated_at": "2023-11-13T03:45:22Z",
     "pushed_at": 1702447995,
     "git_url": "git://github.com/letspl/LETSPL_PUBLIC_REPO3.git",
     "ssh_url": "git@github.com:letspl/LETSPL_PUBLIC_REPO3.git",
     "clone_url": "https://github.com/letspl/LETSPL_PUBLIC_REPO3.git",
     "svn_url": "https://github.com/letspl/LETSPL_PUBLIC_REPO3",
     "homepage": null,
     "size": 76178,
     "stargazers_count": 0,
     "watchers_count": 0,
     "language": "JavaScript",
     "has_issues": true,
     "has_projects": true,
     "has_downloads": true,
     "has_wiki": true,
     "has_pages": false,
     "has_discussions": false,
     "forks_count": 0,
     "mirror_url": null,
     "archived": false,
     "disabled": false,
     "open_issues_count": 0,
     "license": null,
     "allow_forking": true,
     "is_template": false,
     "topics": [],
     "web_commit_signoff_required": false,
     "visibility": "private",
     "forks": 0,
     "open_issues": 0,
     "watchers": 0,
     "default_branch": "master",
     "stargazers": 0,
     "master_branch": "master"
   },
   "pusher": {
     "name": "letspl",
     "email": "help@letspl.me"
   },
   "sender": {
     "login": "letspl",
     "id": 17338824,
     "node_id": "MDQ6VXNlcjE3MzM4ODI0",
     "avatar_url": "https://avatars.githubusercontent.com/u/17338824?v=4",
     "gravatar_id": "",
     "url": "https://api.github.com/users/letspl",
     "html_url": "https://github.com/letspl",
     "followers_url": "https://api.github.com/users/letspl/followers",
     "following_url": "https://api.github.com/users/letspl/following{/other_user}",
     "gists_url": "https://api.github.com/users/letspl/gists{/gist_id}",
     "starred_url": "https://api.github.com/users/letspl/starred{/owner}{/repo}",
     "subscriptions_url": "https://api.github.com/users/letspl/subscriptions",
     "organizations_url": "https://api.github.com/users/letspl/orgs",
     "repos_url": "https://api.github.com/users/letspl/repos",
     "events_url": "https://api.github.com/users/letspl/events{/privacy}",
     "received_events_url": "https://api.github.com/users/letspl/received_events",
     "type": "User",
     "site_admin": false
   },
   "created": false,
   "deleted": false,
   "forced": false,
   "base_ref": null,
   "compare": "https://github.com/letspl/LETSPL_PUBLIC_REPO3/compare/5b23babec5b9...cfbbe7f0e212",
   "commits": [
     {
       "id": "cfbbe7f0e212976c102e47f31b0495dd714f449a",
       "tree_id": "e206908c6efa1b7f374bb6a897bee12199dc9ea5",
       "distinct": true,
       "message": "Update server_config.js",
       "timestamp": "2023-12-13T06:46:11Z",
       "url": "https://github.com/letspl/LETSPL_PUBLIC_REPO3/commit/cfbbe7f0e212976c102e47f31b0495dd714f449a",
       "author": [],
       "committer": [],
       "added": [],
       "removed": [],
       "modified": []
     }
   ],
   "head_commit": {
     "id": "cfbbe7f0e212976c102e47f31b0495dd714f449a",
     "tree_id": "e206908c6efa1b7f374bb6a897bee12199dc9ea5",
     "distinct": true,
     "message": "업데이트함",
     "timestamp": "2023-12-13T06:46:11Z",
     "url": "https://github.com/letspl/LETSPL_PUBLIC_REPO3/commit/cfbbe7f0e212976c102e47f31b0495dd714f449a",
     "author": {
       "name": "letspl",
       "username": "letspl"
     },
     "committer": {
       "name": "GitHub",
       "email": "noreply@github.com",
       "username": "web-flow"
     },
     "added": [],
     "removed": [],
     "modified": [
       "config/server_config.js"
     ]
   }
 }

그러면 이런식으로 채팅으로 연결해도 되구요, 아니면 공지사항/푸시랑 연동해도 되고,

편한방식으로 서비스와 연동이 가능합니다.


3. 피그마 - 빡침주의

  1. 웹훅 연동

  2. 피그마는 기본 팀 프로젝트에서 사용하기 때문에, 외부에 닫혀있기 마련입니다. 따라서 프로필보다는 웹훅으로 연동하는것이 좋겠습니다.

  3. https://www.figma.com/developers/apps

  4. 여기에 들어가시면, 피그마 개발계정 설정이 가능합니다.

  5. 기본적인 정보와 , callbackURL을 등록하면 Client ID과 Client Secret이 발생되고 이를 이용해서 통신을 하는 구조입니다.

역시 패스포트를 찾아봅니다. 좀 불안한 녀석이 하나 있습니다.

주간 다운로드를 보니 , 좀 맛이 간것같긴한데, 일단 figma2가 조금 더 난거 겠죠. 이것을 사용하기로 했습니다.

https://github.com/LiamMartens/passport-figma

https://www.npmjs.com/package/passport-figma2

state라는 값이 갑자기 필요하다고 해서, 뒤늦게 넣긴 했지만, 공식문서에는 사실 없던 내용이라서, 부랴부랴 찾아서 넣었습니다.

하다보면 통신이 잘 안되기도 하고 , 특히 http에는 웹훅을 잘 안보내주기도하고 좀 지치는 부분이 있습니다.

기존패키지를 수정합니다.
'X-FIGMA-TOKEN'로 되어있다면 'Bearer'로 바뀝니다.

node_modules/passport-figma2/lib/passport-figma/strategy.js
this._oauth2._request('GET','https://api.figma.com/v1/me', {'Authorization': 'Bearer ' + accessToken}, "", null, function (err, body, res) {

passport.use(
      new FigmaStrategy(
        {
          clientID: server_config.figma_api.figmaClientId,
          clientSecret: server_config.figma_api.figmaClientSecret,
          callbackURL: server_config.figma_api.figmaCallbackURL,
          state: new Date().getTime(),
        },
        function (accessToken, refreshToken, profile, done) {
          process.nextTick(function () {
            return done(null, profile);
          });
        }
      )
    );

router.get(
  "***********",
  passport.authenticate("figma", {
    scope: "webhooks:write,files:read",
    response_type: "code",
    state: new Date().getTime(),
    //failureRedirect: "/",
  }),
  function (req, res) {
   
    console.log(req);
  }
)

scope는 우선 광범위하게 요청을 해야되는 것 같습니다.

웹훅과 관련된것 뿐만 아니라 파일정보까지 요청을 해야 파일의 변경사항에 대하여 확인이 가능합니다.

그러나 리턴값이 좀 이상하나 날라옵니다. 기대하는 값들은 access_token, refresh_token과, profile 정보가 같이 날라와야 하는데, access_token 하나만 띡 옵니다.

암튼 여러 고비를 넘겨서, 이제 이벤트 훅을 요청할 차례입니다.

피그마는 아래와 같은 이벤트를 지원합니다.

PING,FILE_UPDATE,FILE_VERSION_UPDATE,FILE_DELETE,LIBRARY_PUBLISH,FILE_COMMENT

PING:

이건 그냥 테스트용으로 던져주는 애라서, 별도 등록하지 않아도 , 등록하자마자 날라오는 이벤트입니다.

FILE_UPDATE :

저희가 여러번 테스트했을때보니, 수정 후 화면을 벗어나면 이벤트가 발생합니다.

FILE_VERSION_UPDATE:

이건 파일 버전이 올라가면 보내준다는데, 그닥 쓸모없을 것 같아서 사용하지 않기로 했습니다.

FILE_DELETE

이것도 파일 삭제시 이벤트를 보내주는 것인데, 쓸모없어 보이네요.

LIBRARY_PUBLISH

이건 공개 설정할때 보내주는 이벤트인데, 저희 사이트에는 도움이 될것 같지 않네요

FILE_COMMENT

사용자간에 댓글이 발생하면 이벤트를 보내줍니다. 너무 좋네요 .

이벤트훅을 등록해봅니다.

공식문서는 헤더에 X-Figma-Token 쓰라고 안내하고 있는데, 되기는 개뿔 안됩니다.

Authorization은 버전 1.0에서 쓰던건데, 그걸 쓰니까 됩니다. 이걸로 시간 엄청 허비했습니다.

이벤트 타입이 웹훅 이벤트를 설정하는 부분입니다.

아래 패스워드가 있는데, 2차 검증을 하기 위한 별도 값이어서 설정하셔도 되고, 안해도 됩니다

저희는 그냥 프로젝트 해시값을 패스워드로 지정했습니다.

팀 아이디는 , 피그마에 등록되어있는 team 계정입니다. 이건 사용자분이 꼭 찾아서 넣으셔야 됩니다. 자동 구성이 안됩니다.

{
      url: "https://api.figma.com/v2/webhooks",
      method: "POST",
      headers: {
        Authorization: "Bearer " + token,
        "Content-Type": "application/json",
        "X-Figma-Token": token,
      },
      body: JSON.stringify({
       team_id: "팀 아이디",
       event_type: "FILE_UPDATE",
       endpoint: "URL"
       passcode: "비밀번호",
       status: "ACTIVE",
      })
}

파일 업데이트와 파일 댓글의 데이터 형식이 약간 다릅니다.

전체 틀은 같지만, 댓글은 누가 댓글을 누구에게 어떤 내용으로 썻는지가 포함되어있습니다.

파일 업데이트는 엄청 자주옵니다. 한 5분에 하나씩은 발생하기 때문에

렛플은 오전/오후에 한번씩만 웹훅을 처리하고 있습니다. 계속 채팅내 푸쉬를 하면 엄청 번거롭기도 하고, 감시당하는 느낌이 든다고 하시네요.

하루에 2번정도는 디자인이 변경되고 있구나를 알기 쉬워서 좋은 것 같습니다.

또한 댓글도 바로바로 채팅으로 넘어오니 생각보다 엄청 편하긴 합니다.

다만 페이지내의 세부 단락까지는 넘어가지 않습니다.

예를 들어 LETSPL이라는 페이지에 , 렛플인, 프로젝트 … 이런식으로 세부 단락이 구성되어있는데요

그냥 LETSPL에서 변동이 있다 정도로 넘어옵니다.

코멘트는 저렇게 text만 별도로 빼내서 작업하셔야 합니다. 띄어쓰기가 발생하면 다 저렇게 뭔가 줄을 하나씩 만들어냅니다.

  • 멘션은 특정 누군가를 지칭하지 않으면 데이터가 없긴 합니다.

#파일 업데이트 

{ 
      event_type: 'FILE_UPDATE', 
      file_key: '', 
      file_name: 'Untitled', 
      passcode: '', 
      protocol_version: '2', 
      retries: 0, 
      timestamp: '2023-11-12T07:08:28Z', 
      webhook_id: '' 
} 
 
#파일 내 댓글

{
      "comment": [
        { "mention": "650999239537240607" },
        { "text": " " }, 
        { "mention": "524864095721777940" }, 
        { "text": " 코멘트 발생 \n테스트입니다." }
      ],
      "comment_id": "644402565",
      "created_at": "2023-12-13T07:29:32Z",
      "event_type": "FILE_COMMENT",
      "file_key": "", 
      "file_name": "APP",
      "mentions": [
        { "id": "650999239537240607", "handle": "아이디1" },
        { "id": "524864095721777940", "handle": "아이디2" }
      ],
      "order_id": "",   
      "parent_id": "622139306",   
      "passcode": "", 
      "protocol_version": "2",   
      "resolved_at": "",   
      "retries": 0,   
      "timestamp": "2023-12-13T07:29:32Z",   
      "triggered_by": { "id": "1138649561354795006", "handle": "Letspl_official" },   
      "webhook_id": "" 
   }

연동을 이제 하게 되면, 아래와 같이 파일이 변동이 있거나, 댓글이 신규로 달릴 경우에 , 채팅방이나 공지, 푸시 등으로 알림이 가능합니다.

암튼 검토에서부터 구현까지 모두 해봤습니다. 피그마는 아직 연동된 곳이 정말 적어서, 정말 편하게 잘 쓰고 있습니다.

한번 신규 기능으로 검토해보세요

다음은 ChatGPT 연계 검토한 내용을 올려보도록 하겠습니다.